//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <ddk.h>
#include "Mutex.h"

#define DBGDOC(l, s)    //s

#ifdef __cplusplus
extern "C" {
#endif

#define   SECSZ       512

// control code for ftl level
typedef enum FTLControl {
    FTLControl_DeleteBlock  = 0x10000001,
    FTLControl_Mount        = 0x10000002,
    FTLControl_Unmount      = 0x10000003,
    FTLControl_DFormat      = 0x10000004,
    FTLControl_GetSize      = 0x1,
} FTLControl;

typedef struct FlashInfo {
    UINT32 Size;
    UINT32 EraseUnitSize;
} FlashInfo;

typedef enum FlashControl {
    FlashControl_Open               = 0,
    FlashControl_EraseSector,
    FlashControl_GetInfo,
    FlashControl_MapToUserSpace
} FlashControl;

Address doc_VirtBase = 0;

//global atomic flag
static int g_drvstat = -1;

int doc_read(int part, unsigned char *buffer, unsigned long sector, int nblocks);
int doc_write(int part, unsigned char *buffer, unsigned long sector, int nblocks);
int doc_delete(int part, unsigned long sector, int nblocks);
int doc_dformat(int part);
int doc_mount(int part);
int doc_unmount(int part);
unsigned int doc_getsize(int part);
unsigned int doc_getbootsec(int part);

#ifdef __cplusplus
}
#endif

// Each doc partion corresponds to a minor device
class BlkDoc : public Driver {
public:
    CARAPI Read(
        /* [in] */ UINT64 u64Offset,
        /* [in] */ UINT uNumberOfBytesToRead,
        /* [out] */ EzByteBuf ebbData,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Write(
        /* [in] */ UINT64 u64Offset,
        /* [in] */ EzByteBuf ebbData,
        /* [out] */ UINT * puNumberOfBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Control(
        /* [in] */ INT nControlCode,
        /* [in] */ EzByteBuf ebbInData,
        /* [out] */ EzByteBuf * pOutData,
        /* [out] */ IEvent * * ppCompletionEvent);

    ECode DeleteBlock(
        /* [in] */ UINT sector,
        /* [in] */ UINT nblocks);

    BlkDoc::BlkDoc();
    virtual void Dispose() {};

public:
    friend IDeviceDriver * CDECL CreateDocStorage(uint_t uDeviceNo, void *pvParameter);

private:
	DzMutex Wait;
	WaitResult wr;
    BOOL IsMount;
    int Part;
    unsigned int StartAddr;
    unsigned int Size;
};

BlkDoc::BlkDoc()
{
    IsMount = FALSE;
    Part = -1/*INVALIDHANDLE*/;
    Size = 0;
}

ECode BlkDoc::Read(
        /* [in] */ UINT64 u64Offset,
        /* [in] */ UINT uNumberOfBytesToRead,
        /* [out] */ EzByteBuf ebbData,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    if (!IsMount) return E_ACCESS_DENIED;

    UINT offset = (UINT)u64Offset;
    if (ebbData.IsNull()
        || (offset & 511)
        || (uNumberOfBytesToRead & 511)
        || (!((StartAddr <= offset) && ((offset + uNumberOfBytesToRead) <= (StartAddr + Size))))
        ) {
        DBGDOC(0, kprintf("Invailed Read Argument!\n"));
        return E_UNEXPECTED;
    }

    BYTE *pBuf = (BYTE *)(char *)ebbData;
    BYTE buffer[SECSZ];
    UINT sector,leftsize,rightsize;
    int ret,nblocks, sizeRead;

    sector = RoundUp(offset, SECSZ) >> 9;
    nblocks = ((offset + uNumberOfBytesToRead)>>9) - sector;
    if (nblocks < 0) nblocks=0;
    rightsize = (offset + uNumberOfBytesToRead) % SECSZ;
    offset = offset % SECSZ;
    leftsize = (offset)?SECSZ - offset:0;

    DBGDOC(0, kprintf("Doc:Read: sector=%x, nblocks=%x\n", sector, nblocks));

    sizeRead = 0;
    ret = NOERROR;

	Wait.Lock(&wr);
	if (g_drvstat == 1) {
		kprintf("ERR: DOC Storage can't be accessed! Debug in BlkDoc::Read!\n");
		DebugBreak();
	}
	g_drvstat = 1;

    if (nblocks) {

        if ( (const int)(!(Address)(pBuf + leftsize)) & 3) {
            ret = doc_read(Part, pBuf+leftsize, sector, nblocks);
            if (ret) goto Done;
        }
        else {
            int i;
            for(i=0;i<nblocks;i++) {
                ret = doc_read(Part,buffer,sector+i,1);
                if (ret) goto Done;
                memcpy(pBuf+leftsize+(i<<9),buffer,SECSZ);
            }
        }
        sizeRead += nblocks << 9;
    }
    if (offset) {
        if (uNumberOfBytesToRead < leftsize) {
            leftsize = uNumberOfBytesToRead;
            rightsize = 0;
        }
        ret = doc_read(Part, buffer, sector-1, 1);
        if (ret) goto Done;
        memcpy(pBuf,buffer+offset,leftsize);
        sizeRead += leftsize;

    }
    if (rightsize) {
        ret = doc_read(Part, buffer, sector+nblocks, 1);
        if (ret) goto Done;
        memcpy(pBuf+uNumberOfBytesToRead-rightsize, buffer, rightsize);
        sizeRead += rightsize;
    }

Done:
	g_drvstat = 0;
    Wait.Unlock();
    if (!ret) {
        ebbData.SetUsed(sizeRead);
        return NOERROR;
    }
    else
        return E_UNEXPECTED;
}

ECode BlkDoc::Write(
        /* [in] */ UINT64 u64Offset,
        /* [in] */ EzByteBuf ebbData,
        /* [out] */ UINT * puNumberOfBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    if (!IsMount) return E_ACCESS_DENIED;

    UINT offset, sizeToWrite;
    offset = (UINT)u64Offset;
    sizeToWrite = (UINT)ebbData.GetUsed();
    if (ebbData.IsNull()
        || (0 == sizeToWrite)
        || (offset & 511)
        || (sizeToWrite & 511)
        || (!((StartAddr <= offset) && ((offset + sizeToWrite) <= (StartAddr + Size))))
//        || (NULL == puNumberOfBytesWritten)
        ) {
        DBGDOC(0, kprintf("Invailed Write Argument!\n"));
        return E_UNEXPECTED;
    }

    BYTE buffer[SECSZ];
    UINT sector,leftsize,rightsize,len;
    int ret,nblocks;
    BYTE *pBuf = (BYTE *)(char *)ebbData;

    sector = RoundUp(offset, SECSZ) >> 9;
    nblocks = ((offset + sizeToWrite)>>9) - sector;
    if (nblocks < 0) nblocks=0;
    rightsize = (offset + sizeToWrite) % SECSZ;
    offset = offset % SECSZ;
    leftsize = (offset)?SECSZ - offset:0;

    DBGDOC(1, kprintf("Doc:Write: sector=%x, nblocks=%x\n", sector, nblocks));

    len = 0;
    ret = NOERROR;

    Wait.Lock(&wr);
	if (g_drvstat == 1) {
		kprintf("ERR: DOC Storage can't be accessed! Debug in BlkDoc::Write!\n");
		DebugBreak();
	}
	g_drvstat = 1;

    if (nblocks) {

        if ( (const int)(!(Address)(pBuf + leftsize)) & 3) {

            ret = doc_write(Part, (BYTE*)(pBuf+leftsize), sector, nblocks);
            if (ret) goto Done;
        }
        else {
            int i;
            for(i=0;i<nblocks;i++) {
                memcpy(buffer,pBuf+leftsize+(i<<9),SECSZ);
                ret = doc_write(Part,buffer,sector+i,1);
                if (ret) goto Done;
            }
        }
        len += nblocks << 9;
    }
    if (offset) {
        if (sizeToWrite < leftsize) {
            leftsize = sizeToWrite;
            rightsize = 0;
        }
        ret = doc_read(Part, buffer, sector-1, 1);
        if (ret) goto Done;
        memcpy(buffer+offset,pBuf,leftsize);
        ret = doc_write(Part, buffer, sector-1, 1);
        if (ret) goto Done;

        len += leftsize;

    }
    if (rightsize) {
        ret = doc_read(Part, buffer, sector+nblocks, 1);
        if (ret) goto Done;
        memcpy(buffer, pBuf+sizeToWrite-rightsize, rightsize);
        ret = doc_write(Part, buffer, sector+nblocks, 1);
        if (ret) goto Done;
        len += rightsize;
    }

Done:
	g_drvstat = 0;
    Wait.Unlock();
    if (!ret) {
        if (puNumberOfBytesWritten)
            *puNumberOfBytesWritten = len;
        return NOERROR;
    }
    else {
        return E_UNEXPECTED;
    }
}

ECode BlkDoc::Control(
        /* [in] */ INT nControlCode,
        /* [in] */ EzByteBuf ebbInData,
        /* [out] */ EzByteBuf * pOutData,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    DBGDOC(2, kprintf("Doc:Control: [%x]\n", nControlCode));

    switch(nControlCode) {
        case FTLControl_GetSize: {
            if (pOutData->IsNull()) {
                return E_INVALID_ARGUMENT;
            }
            *(int *)(char *)ebbOutData = Size;
            pOutData->SetUsed(sizeof(int));
            return NOERROR;
            break;
        }
        case FTLControl_DeleteBlock: {
            if (!IsMount) return E_ACCESS_DENIED;

            UINT sector=0;
            UINT nblocks=0;
            memcpy((char *)&sector, (char *)ebbInData, 4);
            memcpy((char *)&nblocks, ((char *)ebbInData)+4, 4);
            return DeleteBlock(sector, nblocks);
            break;
        }
        case FTLControl_Mount: {
            if (IsMount) return E_ACCESS_DENIED;
            if (0 == doc_mount(Part)) {
                IsMount = TRUE;
                return NOERROR;
            }
            else return E_FAIL;
            break;
        }
        case FTLControl_Unmount: {
            if (!IsMount) return E_ACCESS_DENIED;
            if (0 == doc_unmount(Part)) {
                IsMount = FALSE;
                return NOERROR;
            }
            else return E_FAIL;
            break;
        }
        case FTLControl_DFormat: {//DFormat Low Level
            if (IsMount) {
                doc_unmount(Part);
                IsMount = FALSE;
            }
            if (0 == doc_dformat(Part)) {
                return NOERROR;
            }
            else return E_FAIL;
            break;
        }
        default:
            break;
    }
    return E_ACCESS_DENIED;
}

ECode BlkDoc::DeleteBlock(
        /* [in] */ UINT sector,
        /* [in] */ UINT nblocks)
{
    ECode ec;
    Wait.Lock(&wr);
    if (g_drvstat == 1) {
		kprintf("ERR: DOC Storage can't be accessed! Debug in BlkDoc::DeleteBlock!\n");
		DebugBreak();
	}
	g_drvstat = 1;

    ec = doc_delete(Part, sector, nblocks);

    g_drvstat = 0;
    Wait.Unlock();

    return ec;
}

EXTERN IDeviceDriver * CDECL CreateDocStorage(uint_t uDeviceNo, void *pvParameter)
{
    int Part = (int)uDeviceNo;

    //Create a new doc block device
    BlkDoc *pBlkDoc = new BlkDoc();
    if (NULL == pBlkDoc) goto ErrorExit;
    pBlkDoc->AddRef();

    //Map doc window virtual base
    if (doc_VirtBase == 0) {
        doc_VirtBase = (Address)DzKIoRemap(0, 8*1024/*Only need 2k*/);
    }

    kprintf("Create DOC Storage OK, Base [0x%08x]\n", doc_VirtBase);
    assert(0 != doc_VirtBase);

    //Mount the doc
    assert(Part == 0 || Part == 1);
    if (doc_mount(Part) != 0) goto ErrorExit;

    //Get part size

    //Setup device property
    pBlkDoc->Part = Part;
    pBlkDoc->StartAddr = doc_getbootsec(Part);
    pBlkDoc->Size = doc_getsize(Part);
    pBlkDoc->IsMount = TRUE;

    return pBlkDoc;

ErrorExit:
    if (pBlkDoc) delete pBlkDoc;
    return NULL;
}
